#
# Entry for the BRILLIANT Hunger Games competition:
# https://brilliant.org/competitions/hunger-games/
#
# Author: Dain Nilsson <dainzor@gmail.com>
#

from random import random
import math


class Player:

    """
    High-reputation strategy.
    Attempts to keep a high reputation compared to the other players, but to
    not over-do it.
    """

    def reset(self):
        """
        Reset/Initialize counters.
        """
        self.hunt_c = 0
        self.slack_c = 0

    def target_reputation(self, pop_len, player_reputations):
        """
        Calculates our target reputation as the highest reputation of any other
        player that is within one half standard deviation from the mean.
        """
        avg = sum(player_reputations) / pop_len
        var = sum([(rep - avg) ** 2 for rep in player_reputations]) / pop_len
        std_dev = math.sqrt(var)

        max_rep = avg + std_dev * 0.5

        return max([0.0] + [p for p in player_reputations if p <= max_rep])

    def select_hunts(self, pop_len, player_reputations, hunts):
        """
        Select which players to hunt with given the number of times to hunt.
        We select players based on their reputation, favoring a high
        reputation. However, we randomly opt to skip over players with the
        highest reputation, as we assume they are being favored by other
        players and we don't want to give them too much of a head start.
        """
        # Player indexes by reputation, descending.
        indexes = sorted(range(pop_len), key=lambda i: -player_reputations[i])

        # Initialize the list, all slack.
        decisions = ['s'] * pop_len

        # 50% chance of skipping 0, 25% of 1, 12.5% of 2, etc.
        # Skip no more than half the number of slacks.
        skips = 0
        while skips <= (pop_len - hunts) / 2 and random() <= 0.5:
            skips += 1

        for index in indexes[skips:]:
            if hunts > 0:
                hunts -= 1
                decisions[index] = 'h'
            else:
                break

        return decisions

    def hunt_choices(self, round_number, current_food, current_reputation, m,
                     player_reputations):
        """
        Decide who to hunt with.
        We first determine the reputation we wish to have, based on the other
        players. Then, we calculate how many hunts we need to perform this
        round. Lastly, we decide which players to hunt with.

        If there is only one other player, we always slack.

        If it's the first round, we randomly select ~50% of the other players
        to hunt with.
        """
        pop_len = len(player_reputations)

        if pop_len == 1:
            # Only one other player left, try to starve him:
            self.slack_c += 1
            return ['s']
        elif round_number == 1:
            self.reset()

            # First round, we don't know anything about anyone here.
            # Randomly hunt with approximately half the population.
            decisions = ['h' if random() > 0.5 else 's'
                         for x in player_reputations]
            self.hunt_c += decisions.count('h')
            self.slack_c += decisions.count('s')
            return decisions

        # Reputation to beat: the highest reputation of the other players
        # that is less than one standard deviation from the mean.
        target = self.target_reputation(pop_len, player_reputations)

        # Calculate times to hunt for target reputation.
        hunts = 0
        total = self.hunt_c + self.slack_c + pop_len
        while float(self.hunt_c + hunts) / total < target and hunts < pop_len:
            hunts += 1

        # Select players to hunt with. Favor high reputation.
        decisions = self.select_hunts(pop_len, player_reputations, hunts)

        # Keep track of total hunts/slacks.
        self.hunt_c += decisions.count('h')
        self.slack_c += decisions.count('s')

        return decisions

    def hunt_outcomes(self, food_earnings):
        pass  # do nothing

    def round_end(self, award, m, number_hunters):
        pass  # do nothing
